/* $Id: media.c 983 2009-09-01 02:45:42Z phrakt $ */
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <sys/types.h>

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include <canopy/log.h>
#include <canopy/queue.h>
#include <canopy/string.h>

#include "media.h"
#include "private.h"




struct http_media_type {
	char   mt_name[HTTP_MEDIA_MAXTYPELEN];

	union {
		TAILQ_HEAD(, http_media_type) mt_subtypes;
		char  *mt_exts;
	} mt_un;

	struct http_media_type       *mt_parent;

	TAILQ_ENTRY(http_media_type)  mt_link;
};



/* signatures used to guess media types */
static struct http_mediasig {
	u_char   msig[16];
	size_t   msiglen;
	char     mediatype[32];
} media_sigs[] = {
	{ "<!DOCTYPE HTML", 14,   "text/html"         },
	{ "<!doctype html", 14,   "text/html"         },
	{ "<HTML",           5,   "text/html"         },
	{ "<html",           5,   "text/html"         },
	{ "%PDF",            4,   "application/x-pdf" },

	/* this one will always match */
	{ "",                0,   "application/octet-stream"  },
};



static TAILQ_HEAD(, http_media_type) http_media_types;


/*
static int http_ext_cmp (const DBT *, const DBT *);
*/


/*
 * http_media_init()
 *
 */
int
http_media_init(void)
{
/*
	http_file_exts = dbopen(NULL, O_RDWR, 0644, DB_BTREE, &btinfo);
	if (http_file_exts == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to create file extensions "
		    "database");
		return (-1);
	}
*/

	TAILQ_INIT(&http_media_types);
	return (0);
}


/*
 * http_media_addtype()
 *
 * Add a media type <type> to the table of known media types and associate it
 * with the file extensions found in the comma-separated list <exts>.
 */
int
http_media_addtype(const char *type, const char *exts)
{
	char buf[HTTP_MEDIA_MAXTYPELEN], *stp;
	struct http_media_type *mt, *submt;

	if (strlcpy(buf, type, sizeof(buf)) >= sizeof(buf)) {
		cnp_log(CNP_LOG_ERR, "media type `%s' too long", type);
		return (-1);
	}

	if ((stp = strchr(buf, '/')) == NULL) {
		cnp_log(CNP_LOG_ERR, "no / delimiter in media type `%s'",
		    type);
		return (-1);
	}
	*(stp++) = '\0';	/* now we're pointing to the subtype */

	/* look for an existing entry */
	TAILQ_FOREACH(mt, &http_media_types, mt_link)
		if (strcmp(buf, mt->mt_name) == 0)
			break;

	if (mt == NULL) {
		/*
		 * No entry was found so we create one and insert it in
		 * the top-level list.
		 */
		if ((mt = malloc(sizeof(*mt))) == NULL) {
			cnp_log(CNP_LOG_ERRNO, "failed to allocate media "
			    "type entry");
			return (-1);
		}

		strlcpy(mt->mt_name, buf, sizeof(mt->mt_name));
		TAILQ_INIT(&mt->mt_un.mt_subtypes);
		TAILQ_INSERT_TAIL(&http_media_types, mt, mt_link);

	} else {
		/* now look if the subtype is already registered */
		TAILQ_FOREACH(submt, &mt->mt_un.mt_subtypes, mt_link) {
			if (strcmp(stp, submt->mt_name) == 0) {
				cnp_log(CNP_LOG_ERR, "media type `%s' is "
				    "already registered", type);
				return (-1);
			}
		}
	}

	if ((submt = malloc(sizeof(*submt))) == NULL) {
		cnp_log(CNP_LOG_ERR, "failed to allocate media sub entry");
		return (-1);
	}

	strlcpy(submt->mt_name, stp, sizeof(submt->mt_name));
	submt->mt_un.mt_exts = strdup(exts);

	TAILQ_INSERT_TAIL(&mt->mt_un.mt_subtypes, submt, mt_link);

	return (0);
}


/*
 * http_media_gettype()
 *
 * Get the media type of an object by passing
 */

int
http_media_gettype(struct http_entity *ent)
{
	return (0);
}


/*
 * http_media_guess()
 *
 * Attempt to guess the media type of an HTTP entity by looking at the first
 * few bytes of its body.  This function should be used only when the default
 * mediatype <-> extension mapping failed, and the returned value is not
 * guaranteed to be the real media type.  Remember kids, this is just
 * a guess.  First, it checks to see if one of the known signatures matches
 * exactly the one found in the entity <ent>'s body.  If no signature matched
 * exactly, it picks the signature which came the closest by keeping the
 * absolute value of the memcmp() check.  The smallest value should indicate
 * the closest signature.
 */

int
http_media_guess(struct http_entity *ent, char *mtbuf, size_t blen)
{
	int ret, best, best_index;
	u_int i;

	/* just a number larger than the longuest possible match */
	best = 64;
	best_index = -1;

	for (i = 0; i < sizeof(media_sigs)/sizeof(media_sigs[0]); i++) {
		ret = memcmp(ent->ent_body, media_sigs[i].msig,
		    media_sigs[i].msiglen);

		/* only if it's not the `application/octet-stream' entry */
		if ((ret == 0) && (media_sigs[i].msiglen > 0)) {
			strlcpy(ent->ent_media, media_sigs[i].mediatype,
			    sizeof(ent->ent_media));
			return (0);
		} else {
			/* this is borked */
			if (abs(ret) < best) {
				best = abs(ret);
				best_index = i;
			}
		}
	}

	return (0);
}
